package com.lzwork.gpt.service;

import java.net.Inet4Address;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSONObject;
import com.lzwork.gpt.ApiConfig;
import com.lzwork.gpt.db.dao.ModelDbMapper;
import com.lzwork.gpt.db.dao.ModelDbMapperLite;
import com.lzwork.gpt.db.dao.SysInfoMapper;
import com.lzwork.gpt.db.entity.ApiEditEntity;
import com.lzwork.gpt.db.entity.ApiEntity;
import com.lzwork.gpt.db.entity.ApiLogEntity;
import com.lzwork.gpt.db.entity.AppEntity;
import com.lzwork.gpt.db.entity.ModelEntity;
import com.lzwork.gpt.utility.caller.GptCallResult;
import com.lzwork.gpt.utility.caller.GptCaller;
import com.lzwork.gpt.utility.caller.GptCaller.CallType;
import com.lzwork.gpt.utility.common.ParserCaller;
import com.lzwork.utility.logger.LOGGER;
import com.lzwork.utility.pair.StringPair;
import com.lzwork.utility.str.StrUtility;
import com.lzwork.web.JsonKey;
import com.lzwork.web.WebEncoder.UrlEncodeChecker;
import com.lzwork.web.data.BoolResultInfo;
import com.lzwork.web.data.CallResultInfo;
import com.lzwork.web.db.DbEntity;

/**
 * @author LiZheng
 * @date 2023年11月23日 下午2:41:40
 */

@Service
public class ApiDataServiceImpl implements ApiDataService
{
	@Autowired
	private ModelDbMapper dbMapper;

	@Autowired
	private ModelDbMapperLite dbMapperLite;

	@Autowired
	private SysInfoMapper infoMapper;

	private boolean isSqlLite;

	@Value("${spring.datasource.driver-class-name}")
	private String jdbcName;

	@Resource
	Environment environment;

	Random rand = new Random();

	String serverUrl = null;

	final Object sqLiteLocker = new Object();

	@PostConstruct
	public void init()
	{
		if (jdbcName != null && jdbcName.contains("sqlite"))
			isSqlLite = true;

		if (isSqlLite)
		{
			LOGGER.log(LOGGER.DB, "Reseting SqLite id from rowid.");
			dbMapperLite.resetId("model");
			dbMapperLite.resetId("api");
			dbMapperLite.resetId("apilog");
			dbMapperLite.resetId("app");
		}

		VersionUpdate updater = new VersionUpdate(infoMapper, dbMapper);
		updater.versionUpdate();
	}

	@Override
	public String getAccessPath()
	{
		if (serverUrl != null)
		{
			return serverUrl;
		}

		serverUrl = ApiConfig.getProp("staticServer");
		if (StrUtility.stringValid(serverUrl))
			return serverUrl;

		String networkProtocol = "";
		String ip = "";
		String port = "";
		String webApp = "";
		try

		{
			networkProtocol = "http";
			ip = Inet4Address.getLocalHost().getHostAddress();
			port = environment.getProperty("server.port");
			webApp = environment.getProperty("server.servlet.context-path");
			if (webApp == null)
			{
				webApp = "";
			}
		} catch (UnknownHostException e)
		{
			e.printStackTrace();
			// log.error(e.getMessage0);
		}
		// log.info("项目访问地址是:0:// 0:00networkProtocol,ip,port, // webApp);
		serverUrl = networkProtocol + "://" + ip + ":" + port + webApp;
		return serverUrl;
	}

	@Override
	public JSONObject apiCall(ApiEntity api, ModelEntity replaceModel, List<StringPair> paramData, boolean fullReturn,
			boolean dolog)
	{
		if (api == null)
			return JsonKey.errReturn("无效的API.");

		ModelEntity model = replaceModel == null ? getModel(api.getModelid()) : replaceModel;
		if (model == null)
			return JsonKey.errReturn("使用模型不存在：id=" + api.getModelid());

		String prompt = api.getPrompt();
		String callInput = prompt;
		if (!StrUtility.stringValid(callInput))
			return JsonKey.errReturn("Prompt不能为空");

		paramData = api.validateParam(paramData, true);
		callInput = api.makePrompt(paramData, false);

		CallType ct = CallType.parseType(model.getType());
		if (ct == CallType.Unknown)
			return JsonKey.errReturn("Unknown model call type: " + model.getType());

		GptCaller caller;

		if (ct == CallType.OpenApi)
			caller = model.makeOpenApiCaller();
		else
			caller = new ParserCaller(model.getUrl(), dolog);

		JSONObject result = new JSONObject();

		try
		{
			List<String> sys = new ArrayList<>();

			if (StrUtility.stringValid(api.getSystem()))
				sys.add(api.getSystem());

			GptCallResult cr = caller.callChat(callInput, null, sys);
			Exception e = cr.getException();
			if (e != null)
				throw e;

			result.put("response", cr.getResponse());
			result.put("cost", cr.getGpuCost());
			result.put("rawData", cr.getReturnData());

			if (fullReturn)
			{
				result.put("model", model.getId());
				result.put("url", model.getUrl());
				result.put("input", callInput);

				result.put("param", StringPair.toJObjList(paramData, "key", "value"));
				result.put("prompt", prompt);
			}

		} catch (Exception e)
		{
			String err = LOGGER.exceptionInfo(e, false);
			return JsonKey.errReturn(err);
		}

		return result;
	}

	@Override
	public List<ModelEntity> getAllModel(int sort)
	{
		List<ModelEntity> models = dbMapper.getAllModel();

		if (sort == 0)
			return models;

		return DbEntity.sortList(models, sort);
	}

	@Override
	public List<ApiEntity> getAllApi(boolean fullInfo, int sort)
	{
		if (!fullInfo)
			return getAllApiInfo(sort);

		List<ApiEntity> result = dbMapper.getAllApi();

		for (ApiEntity entity : result)
		{
			entity.refreshUrl(getAccessPath());
			entity.refreshParam();
		}

		if (sort == 0)
			return result;

		return DbEntity.sortList(result, sort);
	}

	@Override
	public List<ApiEntity> getAllApiInfo(int sort)
	{
		List<ApiEntity> result = dbMapper.getAllApiInfo();

		// Do not need to refresh contents after load.

		if (sort == 0)
			return result;

		return DbEntity.sortList(result, sort);
	}

	@Override
	public CallResultInfo<ModelEntity> makeModelEntity(ModelEntity model)
	{
		model.setTimeMethod(isSqlLite);
		try
		{
			if (isSqlLite)
			{
				dbMapperLite.createModelL(model);
				dbMapperLite.setModelIdL(model.getId());
			} else
				dbMapper.createModel(model);
			return new CallResultInfo<>(model, "");
		} catch (Exception e)
		{
			e.printStackTrace();
			return new CallResultInfo<>(null, e.getMessage());
		}
	}

	@Override
	public BoolResultInfo setModelEntity(ModelEntity model)
	{
		model.setTimeMethod(isSqlLite);

		try
		{
			dbMapper.updateModel(model);
		} catch (Exception e)
		{
			return BoolResultInfo.makeFalse(e.getMessage());
		}

		return BoolResultInfo.makeTrue("");
	}

	@Override
	public CallResultInfo<ApiEntity> makeApiEntity(ApiEntity api, ApiEditEntity edit)
	{
		api.setTimeMethod(isSqlLite);
		try
		{
			String apikey = api.getTitle() + rand.nextInt();
			apikey += System.currentTimeMillis();
			apikey = StrUtility.simpleMd5Encode(apikey);

			api.setApikey(apikey);

			if (isSqlLite)
			{
				dbMapperLite.createApiL(api);
				dbMapperLite.setApiIdL(api.getId());
			} else
				dbMapper.createApi(api);

			api.refreshUrl(getAccessPath());

			if (edit != null)
			{
				edit.setId(api.getId());
				setApiEditEntity(edit);
			}
			return new CallResultInfo<>(api, "");
		} catch (Exception e)
		{
			e.printStackTrace();
			return new CallResultInfo<>(null, e.getMessage());
		}
	}

	@Override
	public BoolResultInfo setApiEntity(ApiEntity api)
	{
		api.setTimeMethod(isSqlLite);

		try
		{
			dbMapper.updateApi(api);
		} catch (Exception e)
		{
			return BoolResultInfo.makeFalse(e.getMessage());
		}

		return BoolResultInfo.makeTrue("");
	}

	@Override
	public ModelEntity getModel(int id)
	{
		return dbMapper.getModelById(id);
	}

	@Override
	public ApiEntity getApi(int id)
	{
		ApiEntity entity = dbMapper.getApiById(id);

		if (entity != null)
		{
			entity.refreshUrl(getAccessPath());
			entity.refreshParam();
		}
		return entity;
	}

	@Override
	public ApiEntity getApi(String apikey)
	{
		ApiEntity entity = dbMapper.getApiByApikey(apikey);

		if (entity != null)
		{
			entity.refreshUrl(getAccessPath());
			entity.refreshParam();
		}
		return entity;
	}

	@Override
	public BoolResultInfo resetApiKey(int id, String apikey)
	{
		if (!StrUtility.stringValid(apikey))
			return BoolResultInfo.makeFalse("Input apikey is empty.");

		if (apikey.length() >= 50)
			return BoolResultInfo.makeFalse("Api Key length should less than 50. Now: " + apikey.length());

		UrlEncodeChecker checker = new UrlEncodeChecker(apikey);

		if (!checker.getChanges().isEmpty())
		{
			List<String> str = checker.getChangeInfos();
			int total = str.size();
			String cInfo;
			int limit = 3;

			if (total > limit)
			{
				cInfo = StrUtility.combineString(str.subList(0, limit), ", ") + " …… ";
			} else
			{
				cInfo = StrUtility.combineString(str, ", ");
			}

			String info = "Found " + total + " invalid character in new apikey: [" + cInfo + "]";
			return BoolResultInfo.makeFalse(info);
		}

		ApiEntity target = dbMapper.getApiInfoById(id);
		if (target == null)
			return BoolResultInfo.makeFalse("Invalid API target: " + id);
		ApiEntity legacy = dbMapper.getApiInfoByApikey(apikey);
		if (legacy != null)
		{
			String info = "API KEY alreay exist for API: [" + legacy.getId() + "] " + legacy.getTitle();
			return BoolResultInfo.makeFalse(info);
		}

		int set = dbMapper.setApikey(id, apikey);

		return set > 0 ? BoolResultInfo.makeTrue(set + " item updated.") : BoolResultInfo.makeFalse("Set ApiKey failed.");
	}

	@Override
	public BoolResultInfo deleteModel(int id)
	{
		int del = dbMapper.deleteModel(id);

		return del > 0 ? BoolResultInfo.makeTrue(del + " items del.") : BoolResultInfo.makeFalse("Del model failed.");
	}

	@Override
	public BoolResultInfo deleteApi(int id)
	{
		int del = dbMapper.deleteApi(id);
		dbMapper.deleteApiEdit(id);

		return del > 0 ? BoolResultInfo.makeTrue(del + " items del.") : BoolResultInfo.makeFalse("Del api failed.");
	}

	@Override
	public BoolResultInfo addLog(ApiLogEntity log)
	{
		log.setTimeMethod(isSqlLite);
		try
		{
			if (isSqlLite)
			{
				dbMapperLite.addLogL(log);
				dbMapperLite.setLogIdL(log.getId());
			} else
				dbMapper.addLog(log);
			return BoolResultInfo.makeTrue(log.getId());
		} catch (Exception e)
		{
			e.printStackTrace();
			return BoolResultInfo.makeFalse(e.getMessage());
		}
	}

	@Override
	public List<ApiLogEntity> getLogByApi(int api)
	{
		return dbMapper.getLogByApi(api);
	}

	@Override
	public int getLogNumByApi(int api)
	{
		return dbMapper.getLogNum(api);
	}

	@Override
	public BoolResultInfo delLog(int api, int id)
	{
		int del = dbMapper.deleteLog(api, id);

		return del > 0 ? BoolResultInfo.makeTrue(del + " logs del.") : BoolResultInfo.makeFalse("Del log failed.");
	}

	@Override
	public ApiEditEntity getApiEdit(int id)
	{
		ApiEditEntity entity = dbMapper.getApiEditById(id);
		if (entity != null)
			entity.refreshParam();
		return entity;
	}

	@Override
	public BoolResultInfo setApiEditEntity(ApiEditEntity api)
	{
		try
		{
			ApiEditEntity old = getApiEdit(api.getId());
			if (old == null)
				dbMapper.createApiEdit(api);
			else
				dbMapper.updateApiEdit(api);
		} catch (Exception e)
		{
			return BoolResultInfo.makeFalse(e.getMessage());
		}
		return BoolResultInfo.makeTrue("");
	}

	@Override
	public BoolResultInfo deleteApiEdit(int id)
	{
		int del = dbMapper.deleteApiEdit(id);

		return del > 0 ? BoolResultInfo.makeTrue(del + " logs del.") : BoolResultInfo.makeFalse("Del api edit failed.");
	}

	@Override
	public List<AppEntity> getAllApp(boolean fullInfo, int sort)
	{
		if (!fullInfo)
			return getAllAppInfo(sort);

		List<AppEntity> apps = dbMapper.getAllApp();

		if (sort == 0)
			return apps;

		return DbEntity.sortList(apps, sort);
	}

	@Override
	public List<AppEntity> getAllAppInfo(int sort)
	{
		List<AppEntity> apps = dbMapper.getAllAppInfo();

		if (sort == 0)
			return apps;

		return DbEntity.sortList(apps, sort);
	}

	@Override
	public AppEntity getApp(int id)
	{
		AppEntity entity = dbMapper.getAppById(id);

		if (entity != null)
		{
			entity.refreshUrl(getAccessPath());
			entity.refreshParam();
		}
		return entity;
	}

	@Override
	public AppEntity getApp(String appkey)
	{
		AppEntity entity = dbMapper.getAppByAppkey(appkey);

		if (entity != null)
		{
			entity.refreshUrl(getAccessPath());
			entity.refreshParam();
		}
		return entity;
	}

	@Override
	public CallResultInfo<AppEntity> makeAppEntity(AppEntity app)
	{
		app.setTimeMethod(isSqlLite);
		try
		{
			String appkey = app.getTitle() + rand.nextInt();
			appkey += System.currentTimeMillis();
			appkey = StrUtility.simpleMd5Encode(appkey);

			app.setAppkey(appkey);

			if (isSqlLite)
			{
				dbMapperLite.createAppL(app);
				dbMapperLite.setAppIdL(app.getId());
			} else
				dbMapper.createApp(app);

			app.refreshUrl(getAccessPath());

			return new CallResultInfo<>(app, "");
		} catch (Exception e)
		{
			e.printStackTrace();
			return new CallResultInfo<>(null, e.getMessage());
		}
	}

	@Override
	public BoolResultInfo setAppEntity(AppEntity app)
	{
		app.setTimeMethod(isSqlLite);

		try
		{
			dbMapper.updateApp(app);
		} catch (Exception e)
		{
			return BoolResultInfo.makeFalse(e.getMessage());
		}

		return BoolResultInfo.makeTrue("");
	}

	@Override
	public BoolResultInfo deleteApp(int id)
	{
		int del = dbMapper.deleteApp(id);

		return del > 0 ? BoolResultInfo.makeTrue(del + " apps del.") : BoolResultInfo.makeFalse("Del app failed.");
	}
}
